package org.json.sax;

import org.json.sax.JSONParser.JSONParsingException;

/**
 * @class JSONTokenizer 
 * @author diegoyam
 */
public class JSONStringTokenizer implements JSONTokenizer {

	private static final int N = 16;
	private String json; 
	private int at;
	
	public JSONStringTokenizer(String str) {
		json = str;
		at = 0;
	}
	
	public JSONStringTokenizer(JSONTokenizer tokenizer, int tokenizerIndex) {
		at   = tokenizerIndex;
		//json = tokenizer.getJSONString();  // TODO!!!
	}
	
	private String getJSONString() {
		return json;
	}
	
	public String toString() {
		if (more()) {
			char c = json.charAt(at);
			if (c == 0) {
				String sc = "EOF";
				return " at character " + (at+1) + " >>" + sc + "<<"; // + "of " + json;
			}
			String s = json.substring(at, (((at + N) < json.length()) ? at + N : json.length()-1));
			return " at character " + (at+1) + " >>" + s + "...<<"; // + "of " + json;
		}
		else 
			return " at character " + (at+1); // + "of " + json;
	}
	
	/**
	 * Determine if the source string still contains characters that next()
	 * can consume.
	 * @return true if not yet at the end of the source.
	 */
	public boolean more() {
	    return at < json.length();
	}
	
	
	/**
	 * Get the next character in the source string.
	 *
	 * @return The next character, or 0 if past the end of the source string.
	 * @throws JSONParsingException 
	 */
	public char next() throws JSONParsingException {
	//			if (canceled)
	//				throw new JSONParsingException(JSONParsingException.CANCELED);
			
	    if (more()) {
	        char c = json.charAt(at);
	        ++at;
	        return c;
	    }
	    return 0;
	}
	
	/**
	 * Make a JSONException to signal a syntax error.
	 *
	 * @param message The error message.
	 * @return  A JSONException object, suitable for throwing
	 */
	public JSONParsingException syntaxError(String message) {
		back(); // TODO @diegoyam
	    return new JSONParsingException(message + toString());
	}
	
	/**
	 * Back up one character. This provides a sort of lookahead capability,
	 * so that you can test for a digit or letter before attempting to parse
	 * the next number or identifier.
	 */
	public boolean back() {
	    if (at > 0) {
	        --at;
	        return true;
	    }
	    return false;
	}
	
	private int previousIndex() {
		return at - 1;
	}
	
	/**
	 * Get the next char in the string, skipping whitespace
	 * and comments (slashslash, slashstar, and hash).
	 * @throws JSONParsingException
	 * @return  A character, or 0 if there are no more characters.
	 */
	public char nextClean() throws JSONParsingException {
		for (;;) {
			char c = next();
			if (c == '/') {
				switch (next()) {
				case '/':
					do {
						c = next();
					} while (c != '\n' && c != '\r' && c != 0);
					break;
				case '*':
					for (;;) {
						c = next();
						if (c == 0) {
							throw syntaxError("Unclosed comment.");
						}
						if (c == '*') {
							if (next() == '/') {
								break;
							}
							back();
						}
					}
					break;
				default:
					back();
					return '/';
				}
			} else if (c == '#') {
				do {
					c = next();
				} while (c != '\n' && c != '\r' && c != 0);
			} else if (c == 0 || c > ' ') {
				return c;
			}
		}
	}	    
	
	/**
	 * Return the characters up to the next close quote character.
	 * Backslash processing is done. The formal JSON format does not
	 * allow strings in single quotes, but an implementation is allowed to
	 * accept them.
	 * @param quote The quoting character, either
	 *      <code>"</code>&nbsp;<small>(double quote)</small> or
	 *      <code>'</code>&nbsp;<small>(single quote)</small>.
	 * @return      A String.
	 * @throws JSONParsingException Unterminated string.
	 */
	public String nextString(char quote) throws JSONParsingException {
	    char c;
	    StringBuffer sb = new StringBuffer();
	    for (;;) {
	        c = next();
	        switch (c) {
	        case 0:
	        case '\n':
	        case '\r':
	            throw syntaxError("Unterminated string");
	        case '\\':
	            c = next();
	            switch (c) {
	            case 'b':
	                sb.append('\b');
	                break;
	            case 't':
	                sb.append('\t');
	                break;
	            case 'n':
	                sb.append('\n');
	                break;
	            case 'f':
	                sb.append('\f');
	                break;
	            case 'r':
	                sb.append('\r');
	                break;
	            case 'u':
	                sb.append((char)Integer.parseInt(next(4), 16));
	                break;
	            case 'x' :
	                sb.append((char) Integer.parseInt(next(2), 16));
	                break;
	            default:
	                sb.append(c);
	            }
	            break;
	        default:
	            if (c == quote) {
	                return sb.toString();
	            }
	            sb.append(c);
	        }
	    }
	}
	
	/**
	 * Get the next n characters.
	 *
	 * @param n     The number of characters to take.
	 * @return      A string of n characters.
	 * @throws JSONParsingException
	 *   Substring bounds error if there are not
	 *   n characters remaining in the source string.
	 */
	 public String next(int n) throws JSONParsingException {
	     int i = at;
	     int j = i + n;
	     if (j >= json.length()) {
	        throw syntaxError("Substring bounds error");
	     }
	     at += n;
	     return json.substring(i, j);
	 }
	
	 /**
	  * Get the next value. The value can be a Boolean, Double, Integer,
	  * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
	  * @param ignoreNext 
	  * @throws JSONParsingException If syntax error.
	  *
	  * @return An object.
	  */
	 public Object nextValue() throws JSONParsingException {
	     char c = nextClean();
	     String s;
	
	     switch (c) {
	         case '"':
	         case '\'':
	             return nextString(c);
	     }
	
	     /*
	      * Handle unquoted text. This could be the values true, false, or
	      * null, or it can be a number. An implementation (such as this one)
	      * is allowed to also accept non-standard forms.
	      *
	      * Accumulate characters until we reach the end of the text or a
	      * formatting character.
	      */
	
	     StringBuffer sb = new StringBuffer();
	     char b = c;
	     while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
	         sb.append(c);
	         c = next();
	     }
	     back();
	
	     /*
	      * If it is true, false, or null, return the proper value.
	      */
	
	     s = sb.toString().trim();
	     if (s.equals("")) {
	         throw syntaxError("Missing value.");
	     }
	     if (s.equalsIgnoreCase("true")) {
	         return Boolean.TRUE;
	     }
	     if (s.equalsIgnoreCase("false")) {
	         return Boolean.FALSE;
	     }
	     if (s.equalsIgnoreCase("null")) {
	         return null; // TODO Create a NULL Constant
	     }
	
	     /*
	      * If it might be a number, try converting it. We support the 0- and 0x-
	      * conventions. If a number cannot be produced, then the value will just
	      * be a string. Note that the 0-, 0x-, plus, and implied string
	      * conventions are non-standard. A JSON parser is free to accept
	      * non-JSON forms as long as it accepts all correct JSON forms.
	      */
	
	     if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') {
	         if (b == '0') {
	             if (s.length() > 2 && (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
	                 try {
	                     return new Integer(Integer.parseInt(s.substring(2), 16));
	                 } catch (Exception e) { 
	                     /* Ignore the error */
	                 }
	             } else {
	                 try {
	                     return new Integer(Integer.parseInt(s, 8));
	                 } catch (Exception e) {
	                     /* Ignore the error */
	                 }
	             }
	         }
	         try {
	             return Integer.valueOf(s);
	         } catch (Exception e) {
	             try {
	                 return new Long(Long.parseLong(s));
	             } catch (Exception f) {
	                 try {
	                     return Double.valueOf(s);
	                 }  catch (Exception g) {
	                     return s;
	                 }
	             }
	         }
	     }
	     throw syntaxError("Missing value*");
	 }
	
	public int getIndex() {
		return at;
	}

}
